#!/usr/bin/python3
# -*- coding:utf-8 -*-
# @who:     spacelacc
# @when:    2025/04/26
# @what:    GUI for 'probepairer'
# @vers:    V1


from PySide6.QtGui     import QAction
from PySide6.QtCore    import QDir, Qt
from PySide6.QtWidgets import QMainWindow, QHBoxLayout
from PySide6.QtWidgets import QWidget, QStackedWidget
from PySide6.QtWidgets import QPushButton, QVBoxLayout
from PySide6.QtWidgets import QMenu, QFileDialog

from customcubelogo    import CustomCubelogo
from customlistview    import CustomCinListView
from customlistview    import CustomCoutListView
from customdataplot    import CustomDataPlot
from customprobeparser import FileProbeParser

import os
import shutil
import sqlite3


class ProbePairerWidget(QMainWindow):
    
    def __init__(self, width, height, adapter, arch):
        super().__init__()
        self.gw = width
        self.gh = height
        self.arch = arch
        self.adapter = adapter
        self.assembler = []     # data got
        self.probepairs = []    # ultimate output

        # Menubar
        self.menubar = self.menuBar()
        self.menubar.setVisible(False)

        stdin_menu = self.menubar.addMenu('Stdin')

        stdout_menu = self.menubar.addMenu('Stdout')

        plotter_menu = self.menubar.addMenu('Plotter')

        grab_data_d = QAction('Grab Data', self)
        new_base = QAction('Create Database', self)
        drop_base = QAction('Delete Database', self)
        drop_table = QAction('Delete Database Table', self)
        manage_data = QAction('Manage Database Data', self)
        extract_data = QAction('Extract Spreadsheet Data', self)
        base_menu = self.menubar.addMenu('Database')
        base_menu.addAction(grab_data_d)
        base_menu.addSeparator()
        base_menu.addAction(new_base)
        base_menu.addAction(drop_base)
        base_menu.addSeparator()
        base_menu.addAction(drop_table)
        base_menu.addSeparator()
        base_menu.addAction(manage_data)
        base_menu.addSeparator()
        base_menu.addAction(extract_data)

        grab_data_s = QAction('Grab Data', self)
        copy_file = QAction('Copy Spreadsheet', self)
        move_file = QAction('Move Spreadsheet', self)
        rename_file = QAction('Rename Spreadsheet', self)
        merge_data = QAction('Merge Spreadsheets', self)
        split_data = QAction('Split Spreadsheets', self)
        remove_duplicate = QAction('Remove Duplicate', self)
        file_menu = self.menubar.addMenu('Spreadsheet')
        file_menu.addAction(grab_data_s)
        file_menu.addSeparator()
        file_menu.addAction(copy_file)
        file_menu.addAction(move_file)
        file_menu.addAction(rename_file)
        file_menu.addSeparator()
        file_menu.addAction(merge_data)
        file_menu.addAction(split_data)
        file_menu.addSeparator()
        file_menu.addAction(remove_duplicate)

        # Status bar
        self.status = self.statusBar()
        self.status.showMessage('This is status bar')
        self.status.setVisible(False)

        # Core Widgets
        self.splash = CustomCubelogo(self.gw, self.gh, adapter)
        self.stdin = CustomCinListView(self.gw, self.gh, adapter)
        self.stdout = CustomCoutListView(self.gw, self.gh, adapter)
        self.stdin.setObjectName(u'ltProbePairerLin')
        self.stdout.setObjectName(u'ltProbePairerLout')
        self.engine = QWidget()     #ProbePlotterSession()
        self.plotter = CustomDataPlot(self.gw, self.gh, adapter)
        self.prev = QPushButton('<< Prev')
        self.execute = QPushButton('Execute')
        self.next = QPushButton('Next >>')
        self.prev.setObjectName(u'btProbePairerPrev')
        self.execute.setObjectName(u'btProbePairerExecute')
        self.next.setObjectName(u'btProbePairerNext')
        ctbutton = QHBoxLayout()
        ctbutton.setSpacing(9)
        ctbutton.setContentsMargins(6, 6, 6, 6)
        ctbutton.addWidget(self.prev)
        ctbutton.addWidget(self.execute)
        ctbutton.addWidget(self.next)
        ctengine = QVBoxLayout(self.engine)
        ctengine.setSpacing(0)
        ctengine.setContentsMargins(0, 0, 0, 0)
        ctengine.addWidget(self.plotter)
        ctengine.addLayout(ctbutton)

        wtprobepairer = QWidget()
        ctprobepairer = QHBoxLayout(wtprobepairer)
        ctprobepairer.setSpacing(0)
        ctprobepairer.setContentsMargins(0, 0, 0, 0)
        ctprobepairer.addWidget(self.stdin)
        ctprobepairer.addWidget(self.engine)
        ctprobepairer.addWidget(self.stdout)
        ctprobepairer.setStretch(0, 6)
        ctprobepairer.setStretch(1, 30)
        ctprobepairer.setStretch(2, 9)

        self.manager = QStackedWidget()
        self.manager.addWidget(self.splash)
        self.manager.addWidget(wtprobepairer)
        self.setup_context()
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.popup_context)
        self.setCentralWidget(self.manager)
        self.resize(self.gw, self.gh)

        # connecting signals
        self.splash.signalSplashDone.connect(self.signalSplashDoneHandler)
        self.prev.clicked.connect(self.probe_pairer_plot_prev)
        self.execute.clicked.connect(self.probe_pairer_execute)
        self.next.clicked.connect(self.probe_pairer_plot_next)
        self.stdout.signalProbePlot.connect(self.plotter.plot)
        grab_data_s.triggered.connect(self.grab_data_from_spreadsheet)
        copy_file.triggered.connect(self.copy_spreadsheet)
        move_file.triggered.connect(self.move_spreadsheet)
        rename_file.triggered.connect(self.rename_spreadsheet)
        merge_data.triggered.connect(self.merge_spreadsheets)
        split_data.triggered.connect(self.split_spreadsheet)
        remove_duplicate.triggered.connect(self.remove_duplicate_data)
        grab_data_d.triggered.connect(self.grab_data_from_database)
        new_base.triggered.connect(self.create_database)
        drop_base.triggered.connect(self.delete_database)
        drop_table.triggered.connect(self.delete_database_table)
        manage_data.triggered.connect(self.manage_database_data)
        extract_data.triggered.connect(self.extract_spreadsheet_data)

    def setup_context(self):
        self.context = QMenu(self)
        self.show_menubar = QAction('Show Menubar', self, checkable=True)
        self.show_statusbar = QAction('Show Statusbar', self, checkable=True)
        self.enable_stdin_context = QAction('Enable Stdin Context', self, checkable=True)
        self.enable_stdout_context = QAction('Enable Stdout Context', self, checkable=True)
        self.enable_plotter_context = QAction('Enable Plotter Context', self, checkable=True)
        self.show_menubar.setChecked(True)
        self.show_statusbar.setChecked(True)
        self.enable_stdin_context.setChecked(True)
        self.enable_stdout_context.setChecked(True)
        self.enable_plotter_context.setChecked(True)
        self.show_menubar.toggled.connect(self.toggle_menubar)
        self.show_statusbar.toggled.connect(self.toggle_statusbar)
        self.enable_stdin_context.toggled.connect(self.toggle_stdin_context)
        self.enable_stdout_context.toggled.connect(self.toggle_stdout_context)
        self.enable_plotter_context.toggled.connect(self.toggle_plotter_context)

        self.context.addAction(self.show_menubar)
        self.context.addAction(self.show_statusbar)
        self.context.addSeparator()
        self.context.addAction(self.enable_stdin_context)
        self.context.addAction(self.enable_stdout_context)
        self.context.addAction(self.enable_plotter_context)

    def popup_context(self, pos):
        if self.manager.currentIndex() == 0:
            return
        self.context.exec(self.mapToGlobal(pos))

    def toggle_menubar(self):
        if self.show_menubar.isChecked():
            self.menubar.setVisible(True)
        else:
            self.menubar.setVisible(False)

    def toggle_statusbar(self):
        if self.show_statusbar.isChecked():
            self.status.setVisible(True)
        else:
            self.status.setVisible(False)

    def toggle_stdin_context(self):
        if self.enable_stdin_context.isChecked():
            self.stdin.list_view_stdin_enable_context()
        else:
            self.stdin.list_view_stdin_disable_context()

    def toggle_stdout_context(self):
        pass

    def toggle_plotter_context(self):
        pass

    def signalSplashDoneHandler(self):
        self.menubar.setVisible(True)
        self.status.setVisible(True)
        self.stdin.list_view_stdin_enable_context()
        self.manager.setCurrentIndex(1)

    def probe_pairer_execute(self):
        # Once executed, all old pairs will be cleared
        self.probepairs.clear()

        self.assembler = self.stdin.list_view_stdin_data()
        probes_been_coupled = []
        eval_current_coupled = []
        eval_couldnt_coupled = []

        eval_exact_coupled = []   # fully coupled
        eval_loose_coupled = []   # fuzzy coupled
        eval_fails_coupled = []   # cannot coupled

        count = -1
        for probe in self.assembler:
            count += 1
            # Has been coupled, skip it
            if probe in probes_been_coupled:
                continue

            # Search for possible coupler matches
            for candidate in self.assembler[count+1:]:
                # Not selected, skip it
                if not candidate[2]:
                    continue
                # Has been coupled, skip it
                if candidate in probes_been_coupled:
                    continue
                # evaluator the same, may be coupled
                if probe[-3] == candidate[-3]:
                    eval_current_coupled.append(candidate)

            # No couple found, probe cannot couple
            if not eval_current_coupled:
                if probe not in eval_couldnt_coupled:
                    eval_couldnt_coupled.append(probe)

                # Go for next probe
                continue

            # Possible couples found, choose the best one
            exact_eval = 1000.0
            exact_fit  = eval_current_coupled[0]
            for candidate in eval_current_coupled[1:]:
                if probe[-1] < exact_eval:
                    exact_fit  = candidate
                    exact_eval = candidate[-1]
            # Best couple found
            probes_been_coupled.append(probe)
            probes_been_coupled.append(exact_fit)
            if probe not in eval_exact_coupled:
                eval_exact_coupled.append(probe)
            if exact_fit not in eval_exact_coupled:
                eval_exact_coupled.append(exact_fit)

            # Clear temporary possible couples for next iter
            eval_current_coupled.clear()

        # Now, starting for fuzzy couples
        probes_been_coupled.clear()
        count = -1
        for c1 in eval_couldnt_coupled:
            count += 1
            eval_current_coupled.clear()
            # Not chosen for couple, skip it
            if not c1[2]:
                continue
            # Has been coupled, skipt it
            if c1 in probes_been_coupled:
                continue

            for c2 in eval_couldnt_coupled[count+1:]:
                if not c2[2]:
                    continue
                if c2 in probes_been_coupled:
                    continue
                # fuzzy match
                if c1[-4] == c2[-4]:
                    eval_current_coupled.append(c2)

            # Check for best couple, if not, then this
            # probe will fail to be coupled
            if not eval_current_coupled:
                if c1 not in eval_fails_coupled:
                    eval_fails_coupled.append(c1)
                continue

            # Search for best couple
            fuzzy_eval = 1000.0
            fuzzy_fit  = eval_current_coupled[0]
            for c in eval_current_coupled[1:]:
                if c[-2] < fuzzy_eval:
                    fuzzy_fit  = c
                    fuzzy_eval = c[-2]
            probes_been_coupled.append(c1)
            probes_been_coupled.append(fuzzy_fit)
            if c1 not in eval_loose_coupled:
                eval_loose_coupled.append(c1)
            if fuzzy_fit not in eval_loose_coupled:
                eval_loose_coupled.append(fuzzy_fit)

        # All left will be 'eval_fails_coupled'

        # Now, we can get the ultimate answer.
        for i in range(0, len(eval_exact_coupled), 2):
            p1 = eval_exact_coupled[i]
            p2 = eval_exact_coupled[i+1]
            self.probepairs.append([0, p1, p2])
        for i in range(0, len(eval_loose_coupled), 2):
            p1 = eval_loose_coupled[i]
            p2 = eval_loose_coupled[i+1]
            self.probepairs.append([1, p1, p2])
        for p in eval_fails_coupled:
            self.probepairs.append([2, p, p])

        self.stdout.list_view_stdout_renew(self.probepairs)

    def probe_pairer_plot_prev(self):
        self.stdout.prev()

    def probe_pairer_plot_next(self):
        self.stdout.next()



    def grab_data_from_spreadsheet(self):
        dialog = FileProbeParser()
        dialog.resize(0.9*self.gw, 0.9*self.gh)
        dialog.signalDataDone.connect(self.stdin.list_view_stdin_update)
        retvalue = dialog.exec()
        count = self.stdin.list_view_stdin_update_count()
        if count > 0:
            self.status.showMessage(f'Just update {count} probes')
        else:
            self.status.showMessage('')

    def copy_spreadsheet(self):
        files, _ = QFileDialog.getOpenFileNames(self, "Select Files to Copy", QDir.currentPath(), '(*)')
        if not files:
            return
        for file in files:
            if os.path.dirname(file) == self.arch['filebase']:
                continue
            if not os.path.basename(file).endswith('xls'):
                print("not proper format")
                continue
            shutil.copy2(file, self.arch['filebase'])

    def move_spreadsheet(self):
        files, _ = QFileDialog.getOpenFileNames(self, "Select Files to Move", QDir.currentPath(), '(*)')
        if not files:
            return
        for file in files:
            if os.path.dirname(file) == self.arch['filebase']:
                continue
            if not os.path.basename(file).endswith('xls'):
                print("not proper format")
                continue
            shutil.move(file, self.arch['filebase'])

    def rename_spreadsheet(self):
        pass

    def merge_spreadsheets(self):
        print("ready to merge selected spreadsheets into one")

    def split_spreadsheet(self):
        print("ready to split one spreadsheet into multiple others by time")

    def remove_duplicate_data(self):
        print("ready to remove duplicated or not needed probes from spreadsheet")


    # Here Starts dealing with 'database' menu
    def grab_data_from_database(self):
        pass

    def create_database(self):
        file, _ = QFileDialog.getSaveFileName(self, "Create Database", self.arch['basebase'], '(*.*)')
        if not file:
            return
        conn = sqlite3.connect(file)
        conn.close()

    def delete_database(self):
        file, _ = QFileDialog.getOpenFileName(self, "Select Database to Delete", self.arch['basebase'], '(*.db)')
        if not file:
            return
        os.remove(file)


    def delete_database_table(self):
        pass

    def manage_database_data(self):
        pass

    def extract_spreadsheet_data(self):
        pass
